﻿using System;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Xml.Linq;
using Emico.Linq.Criterion.Serialization;

namespace Emico.Linq.Criterion
{
    [Serializable]
    public class LambdaExpression<T, A0, A1, A2, A3, A4, A5, A6, A7> : LambdaExpression<T>
        where T : class
    {
        public LambdaExpression(Expression<Func<T, A0, A1, A2, A3, A4, A5, A6, A7, bool>> predicate, A0 arg0, A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5, A6 arg6, A7 arg7)
            : base(predicate, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
        {
        }
        protected LambdaExpression(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

    [Serializable]
    public class LambdaExpression<T, A0, A1, A2, A3, A4, A5, A6> : LambdaExpression<T>
        where T : class
    {
        public LambdaExpression(Expression<Func<T, A0, A1, A2, A3, A4, A5, A6, bool>> predicate, A0 arg0, A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5, A6 arg6)
            : base(predicate, arg0, arg1, arg2, arg3, arg4, arg5, arg6)
        {
        }
        protected LambdaExpression(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

    [Serializable]
    public class LambdaExpression<T, A0, A1, A2, A3, A4, A5> : LambdaExpression<T>
        where T : class
    {
        public LambdaExpression(Expression<Func<T, A0, A1, A2, A3, A4, A5, bool>> predicate, A0 arg0, A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5)
            : base(predicate, arg0, arg1, arg2, arg3, arg4, arg5)
        {
        }
        protected LambdaExpression(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

    [Serializable]
    public class LambdaExpression<T, A0, A1, A2, A3, A4> : LambdaExpression<T>
        where T : class
    {
        public LambdaExpression(Expression<Func<T, A0, A1, A2, A3, A4, bool>> predicate, A0 arg0, A1 arg1, A2 arg2, A3 arg3, A4 arg4)
            : base(predicate, arg0, arg1, arg2, arg3, arg4)
        {
        }
        protected LambdaExpression(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

    [Serializable]
    public class LambdaExpression<T, A0, A1, A2, A3> : LambdaExpression<T>
        where T : class
    {
        public LambdaExpression(Expression<Func<T, A0, A1, A2, A3, bool>> predicate, A0 arg0, A1 arg1, A2 arg2, A3 arg3)
            : base(predicate, arg0, arg1, arg2, arg3)
        {
        }
        protected LambdaExpression(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

    [Serializable]
    public class LambdaExpression<T, A0, A1, A2> : LambdaExpression<T>
        where T : class
    {
        public LambdaExpression(Expression<Func<T, A0, A1, A2, bool>> predicate, A0 arg0, A1 arg1, A2 arg2)
            : base(predicate, arg0, arg1, arg2)
        {
        }
        protected LambdaExpression(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

    [Serializable]
    public class LambdaExpression<T, A0, A1> : LambdaExpression<T>
        where T : class
    {
        public LambdaExpression(Expression<Func<T, A0, A1, bool>> predicate, A0 arg0, A1 arg1)
            : base(predicate, arg0, arg1)
        {
        }
        protected LambdaExpression(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

    [Serializable]
    public class LambdaExpression<T, A0> : LambdaExpression<T>
        where T : class
    {
        public LambdaExpression(Expression<Func<T, A0, bool>> predicate, A0 arg0)
            : base(predicate, arg0)
        {
        }

        protected LambdaExpression(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

    [Serializable]
    public class LambdaExpression<T> : AbstractExpression<T>
        where T : class
    {
        #region ParameterModifier

        class ParameterModifier : ExpressionVisitor
        {
            ParameterExpression _parameter;
            LambdaExpression _expression;
            object[] _args;

            public ParameterModifier(LambdaExpression expression, ParameterExpression parameter, object[] args)
            {
                this._expression = expression;
                this._parameter = parameter;
                this._args = args;
            }

            public Expression Modify()
            {
                return Visit(this._expression);
            }

            protected override Expression VisitParameter(ParameterExpression p)
            {
                Expression paramExpression = null;
                if (this._expression.Parameters[0].Name == p.Name)
                {
                    paramExpression = base.VisitParameter(this._parameter);
                }


                for (int i = 1; i < this._expression.Parameters.Count; i++)
                {
                    ParameterExpression parm = this._expression.Parameters[i];
                    if (parm.Name == p.Name)
                    {
                        object value = this._args[i - 1];
                        paramExpression = Expression.Constant(value, parm.Type);
                    }
                }

                return paramExpression;
            }
        }

        #endregion

        public LambdaExpression Predicate
        {
            get;
            private set;
        }

        public object[] Args
        {
            get;
            private set;
        }

        public LambdaExpression(Expression<Func<T, bool>> predicate)
            : this(predicate, new object[0])
        {
        }

        protected LambdaExpression(LambdaExpression predicate, params object[] args)
        {
            if (predicate == null)
            {
                throw new ArgumentNullException("predicate");
            }

            this.Predicate = predicate;
            this.Args = args;
        }

        protected LambdaExpression(SerializationInfo info, StreamingContext context)
        {
            this.Args = (object[])info.GetValue("Args", typeof(object[]));
                        
            XElement xe = XElement.Parse(info.GetString("Predicate"));
            ExpressionSerializer serializer = new ExpressionSerializer();
            this.Predicate = (LambdaExpression)serializer.Deserialize(xe);
        }

        #region Implement Abstract Method
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Args", this.Args);

            ExpressionSerializer serializer = new ExpressionSerializer();
            XElement xe = serializer.Serialize(this.Predicate);
            info.AddValue("Predicate", xe.ToString());
        }

        public override Expression ToLinqExpression(Criteria<T> criteria, ParameterExpression parameter)
        {
            Expression filter = ((LambdaExpression)ModifyExpressionParameter(this.Predicate, parameter, this.Args)).Body;
            return filter;
        }

        #endregion

        private static Expression ModifyExpressionParameter(LambdaExpression expression, ParameterExpression parameter, object[] args)
        {
            ParameterModifier modifier = new ParameterModifier(expression, parameter, args);
            return modifier.Modify();
        }
    }
}
